package Address;

import utils.Base58;
import utils.Hash;

import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.util.Arrays;

/**
 * @author:zhengyilong
 * @email:1797344574@qq.com
 * @phone:13479394730
 * @desc:该类用于推导生成原始格式的比特币地址
 * @DateTime:2021/12/20 14:28
 **/
public class Address {

    /*
       该方法用于生成一对随机的秘钥，并将生成的密钥对返回
       @return 生成的秘钥对对象
     */
    public KeyPair generateKey(){
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
            //secp256k1,secp256r1   参数类型
            ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
            //进行初始化
            generator.initialize(spec);
            //获取密钥对并将其返回
            return generator.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
    该方法用于根据一对给定的秘钥数据推导生成对应的原始格式的比特币地址
    参数：keyPair 生成的密钥对
    返回值：推导生成的比特币地址
     */
    public String newAddress(KeyPair keyPair){
        //将PublicKey类型强行转换成ECPublicKey
        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        //对公钥的数据进行计算
        //原始格式公钥：04 + x坐标(32字节) + y坐标(32字节)
        //获取到那个点
        ECPoint point = publicKey.getW();

        //原始格式的公钥
        byte[] pubKey = toUncompressPubKey(point);

        //对公钥进行哈希计算
        byte[] pubKeyHash256 = Hash.SHA256(pubKey);
        byte[] pubHash = Hash.RIPEMED160(pubKeyHash256);

        byte[] version = new byte[1];
        version[0] = 0x00;//版本号

        //添加版本号
        byte[] ver_pubhash = new byte[version.length + pubHash.length];
        System.arraycopy(version,0,ver_pubhash,0,version.length);
        System.arraycopy(pubHash,0,ver_pubhash,1,pubHash.length);

        //双哈希，取前四个字节
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);
        byte[] check = new byte[4];
        System.arraycopy(hash2,0,check,0,check.length);

        //将ver_pubhash与校验码拼接
        byte[] ver_pubhash_check = new byte[ver_pubhash.length + check.length];
        System.arraycopy(ver_pubhash,0,ver_pubhash_check,0,ver_pubhash.length);
        System.arraycopy(check,0,ver_pubhash_check,ver_pubhash.length,check.length);

        //对拼接到的字节数组进行base58编码
        return Base58.encode(ver_pubhash_check);
    }

    /**
     * 校验比特币地址是否符合规范，并返回校验的结果
     * @param addr 要校验的字符串类型的地址
     * @return 校验结果：true表示符合规范 false表示不符合规范
     */
    public boolean validateAddr(String addr){
        if ("".equals(addr) || addr == null){
            return false;
        }
        byte[] ver_pubhash_check = Base58.decode(addr);
        if (ver_pubhash_check.length < 4){
            return false;
        }

        //获取校验码
        byte[] check = new byte[4];
        System.arraycopy(ver_pubhash_check,ver_pubhash_check.length-4,check,0,4);

        //获取到去掉校验码的值
        byte[] ver_pubhash = new byte[ver_pubhash_check.length-4];
        System.arraycopy(ver_pubhash_check,0,ver_pubhash,0,ver_pubhash.length);

        //双hash，取前4个字节
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);
        if (hash2 == null){
            return false;
        }
        byte[] check_sum = new byte[4];
        System.arraycopy(hash2,0,check_sum,0,check_sum.length);

        //比价两个校验码（字节数组）是否相等
        return Arrays.equals(check,check_sum);
    }

    /**
     * 该方法用于生成非压缩格式的公钥并返回
     * @return 非压缩性格式公钥
     */
    public static byte[] toUncompressPubKey(ECPoint point){
        byte[] xBytes = point.getAffineX().toByteArray();
        byte[] yBytes = point.getAffineY().toByteArray();
        byte[] pubKey = new byte[1+2*32];
        pubKey[0] = 04;
        //x坐标
        if (xBytes.length == 32){
            //通过拷贝来完成数组的拼接
            /**
             * 第一个参数：拷贝的原数组
             * 第二个参数：从原数组中的哪个位置开始拷贝
             * 第三个参数：将数组拷贝到哪里去
             * 第四个参数：从目的地的哪个位置开始放
             * 第五个参数：拷贝的长度
             */
            System.arraycopy(xBytes,0,pubKey,1,xBytes.length);
        }else if(xBytes.length > 32){
            //只取后32个字节
            System.arraycopy(xBytes,1,pubKey,1,xBytes.length-1);
        }
        //y坐标
        if (yBytes.length == 32){
            System.arraycopy(yBytes,0,pubKey,33,yBytes.length);
        }else if(yBytes.length > 32){
            System.arraycopy(yBytes,1,pubKey,33,yBytes.length-1);
        }

        return pubKey;
    }

}

